package scenetracer.math;

import java.util.LinkedList;

import scenetracer.core.Primitive;
import scenetracer.core.Ray;
import scenetracer.core.primitive.IntersectionState;
import scenetracer.core.primitive.Triangle;
import scenetracer.core.primitive.TriangleMesh;

public class BoundingBox extends Primitive{

	private final float _EPSILON = 10e-5F;
	private final long _IN = 0;
	private final long _OUT = 1;
	
	public TriangleMesh mesh = null;
	Point3[] boxCorners = new Point3[8];

	public Point3 min, max;
	Point3 center = new Point3();

	public BoundingBox()
	{

	}

	public BoundingBox(TriangleMesh mesh)
	{
		this(mesh.min, mesh.max);
	}

	public BoundingBox(Point3 min, Point3 max)
	{
		this.mesh = new TriangleMesh();
		this.min = min;
		this.max = max;
		center.x = min.x + (max.x - min.x) / 2;
		center.y = min.y + (max.y - min.y) / 2;
		center.z = min.z + (max.z - min.z) / 2;
	}

	
	public void setMesh(TriangleMesh mesh)
	{
		this.mesh = mesh;
	}

	public float getBoxLenght()
	{
		return max.x - min.x;
	}

	public float getBoxHeight()
	{
		return max.y - min.y;
	}

	public float getBoxDepth()
	{
		return max.z - min.z;
	}

	private boolean intersects(Point3 origin, Vector3 orientation, float t0, float t1)
	{
	
		float tmin, tmax, tymin, tymax, tzmin, tzmax;
	
		if (orientation.x >= 0)
		{
			tmin = (min.x - origin.x) / orientation.x;
			tmax = (max.x - origin.x) / orientation.x;
		}
		else
		{
			tmin = (max.x - origin.x) / orientation.x;
			tmax = (min.x - origin.x) / orientation.x;
		}
	
		if (orientation.y >= 0)
		{
			tymin = (min.y - origin.y) / orientation.y;
			tymax = (max.y - origin.y) / orientation.y;
		}
		else
		{
			tymin = (max.y - origin.y) / orientation.y;
			tymax = (min.y - origin.y) / orientation.y;
		}
	
		if ((tmin > tymax) || (tymin > tmax))
			return false;
		if (tymin > tmin)
			tmin = tymin;
		if (tymax < tmax)
			tmax = tymax;
		if (orientation.z >= 0)
		{
			tzmin = (min.z - origin.z) / orientation.z;
			tzmax = (max.z - origin.z) / orientation.z;
		}
		else
		{
			tzmin = (max.z - origin.z) / orientation.z;
			tzmax = (min.z - origin.z) / orientation.z;
		}
		if ((tmin > tzmax) || (tzmin > tmax))
			return false;
		if (tzmin > tmin)
			tmin = tzmin;
		if (tzmax < tmax)
			tmax = tzmax;
	
		return ((tmin < t1) && (tmax > t0));
	}

	public boolean intersects(Point3 origin, Vector3 orientation)
	{
		if(intersectDistance(origin, orientation) != null)
		{
			return true;
		}

		return false;

	}

	public boolean isInside(Point3 point)
	{
		return (point.x <= max.x) && (point.x >= min.x) && (point.y <= max.y)
			&& (point.y >= min.y) && (point.z <= max.z) && (point.z >= min.z);
	}
	
	
	public boolean intersects(Triangle t) {
		IntersectionState intersection;
		Vector3 d1 = new Vector3(t.e1);
		float d1Length = d1.length();
		d1.normalize();		
		
		intersection = intersectDistance(t.points[0], d1);
		
		if( intersection != null ) {
			if( intersection.getIntersectionPoint().distanceTo(t.points[0]) <= d1Length ) {
				return true;
			}
		}
		
		Vector3 d2 = new Vector3(t.e2);
		float d2Length = d2.length();
		d2.normalize();		
		
		intersection = intersectDistance(t.points[0], d2);
		
		if( intersection != null ) {
			if( intersection.getIntersectionPoint().distanceTo(t.points[0]) <= d2Length ) {
				return true;
			}
		}
		
		Vector3 d3 = Vector3.sub(t.points[2],t.points[1]);
		
		float d3Length = d3.length();
		d3.normalize();		
		
		intersection = intersectDistance(t.points[1], d3);
		
		if( intersection != null ) {

			if( intersection.getIntersectionPoint().distanceTo(t.points[1]) <= d3Length ) {
				return true;
			}
		}
				
		return false;
	}
	


	public LinkedList<BoundingBox> divideOctTree()
	{
		Point3 max1, min1, max2, min2, max3, min3, max4, min4, max5, min5, max6, min6, max7, min7, max8, min8;
		LinkedList<BoundingBox> ret = new LinkedList<BoundingBox>();
		
		float w = getBoxLenght();	
		float h = getBoxHeight();
		float d = getBoxDepth();
		float factor = 2;
		
		min1=new Point3(min.x,min.y + h/factor ,min.z);
		max1=new Point3(min.x + w/factor ,max.y,min.z + d/factor);
	
		min2=new Point3(min.x + w/factor,min.y + h/factor ,min.z);
		max2=new Point3(max.x ,max.y, min.z + d/factor);
		
		min3=new Point3(min.x,min.y,min.z);
		max3=new Point3(min.x + w/factor ,min.y + h/factor,min.z + d/factor);
	
	
		min4=new Point3(min.x + w/factor,min.y  ,min.z);
		max4=new Point3(max.x  ,min.y + h/factor,min.z + d/factor);
	
		min5=new Point3(min.x,min.y + h/factor ,min.z + d/factor);
		max5=new Point3(min.x + w/factor ,max.y,max.z);
		
		min6=new Point3(min.x + w/factor,min.y + h/factor ,min.z + d/factor);
		max6=new Point3(max.x,max.y,max.z );
		
		min7=new Point3(min.x + w/factor,min.y  ,min.z + d/factor);
		max7=new Point3(max.x  ,min.y + h/factor,max.z);
		
		min8=new Point3(min.x,min.y ,min.z + d/factor);
		max8=new Point3(min.x + w/factor ,min.y + h/factor,max.z);
	
		ret.add(new BoundingBox(min1, max1));
		ret.add(new BoundingBox(min2, max2));
		ret.add(new BoundingBox(min3, max3));
		ret.add(new BoundingBox(min4, max4));
		ret.add(new BoundingBox(min5, max5));
		ret.add(new BoundingBox(min6, max6));
		ret.add(new BoundingBox(min7, max7));
		ret.add(new BoundingBox(min8, max8));
	
		return ret;
	}

	private long bevel_3d(Point3 p)
	{
		long outcode;
	
		outcode = 0;
		if ((p.x + p.y + p.z) > 1.5f)
			outcode |= 0x01;
		if ((p.x + p.y - p.z) > 1.5f)
			outcode |= 0x02;
		if ((p.x - p.y + p.z) > 1.5f)
			outcode |= 0x04;
		if ((p.x - p.y - p.z) > 1.5f)
			outcode |= 0x08;
		if ((-p.x + p.y + p.z) > 1.5f)
			outcode |= 0x10;
		if ((-p.x + p.y - p.z) > 1.5f)
			outcode |= 0x20;
		if ((-p.x - p.y + p.z) > 1.5f)
			outcode |= 0x40;
		if ((-p.x - p.y - p.z) > 1.5f)
			outcode |= 0x80;
		return (outcode);
	}

	private float max3(float a, float b, float c)
	{
		return ((a > b) && (a > c)) ? a : ((b > c) ? b : c);
	}

	private long check_point(Point3 p1, Point3 p2, float alpha, long mask)
	{
		Point3 plane_point = new Point3();

		plane_point.x = (float) lerp(alpha, (float) p1.x, (float) p2.x);
		plane_point.y = (float) lerp(alpha, (float) p1.y, (float) p2.y);
		plane_point.z = (float) lerp(alpha, (float) p1.z, (float) p2.z);

		return (face_plane(plane_point) & mask);
	}


	private long face_plane(Point3 p)
	{
		long outcode;
		float halfbox_x = (max.x - min.x) / 2.0F;
		float halfbox_y = (max.y - min.y) / 2.0F;
		float halfbox_z = (max.z - min.z) / 2.0F;
	
		outcode = 0;
		if (p.x > halfbox_x)
			outcode |= 0x01;
		if (p.x < -halfbox_x)
			outcode |= 0x02;
		if (p.y > halfbox_y)
			outcode |= 0x04;
		if (p.y < -halfbox_y)
			outcode |= 0x08;
		if (p.z > halfbox_z)
			outcode |= 0x10;
		if (p.z < -halfbox_z)
			outcode |= 0x20;
		return (outcode);
	}

	private long sign3(Point3 a)
	{
		long res1 = (a.x < _EPSILON) ? 4 : 0;
		long res2 = (a.x > -_EPSILON) ? 32 : 0;
		long res3 = (a.y < _EPSILON) ? 2 : 0;
		long res4 = (a.y > -_EPSILON) ? 16 : 0;
		long res5 = (a.z < _EPSILON) ? 1 : 0;
		long res6 = (a.z > -_EPSILON) ? 8 : 0;
	
		return (res1 | res2 | res3 | res4 | res5 | res6);
	}

	private long check_line(Point3 p1, Point3 p2, long outcode_diff)
	{

		if ((0x01 & outcode_diff) != 0)
		{
			if (check_point(p1, p2, (float) ((0.5 - p1.x) / (p2.x - p1.x)), 0x3e) == _IN)
				return (_IN);
		}
		if ((0x02 & outcode_diff) != 0)
		{
			if (check_point(p1, p2, (float) ((-.5 - p1.x) / (p2.x - p1.x)), 0x3d) == _IN)
				return (_IN);
		}
		if ((0x04 & outcode_diff) != 0)
		{
			if (check_point(p1, p2, (float) ((.5 - p1.y) / (p2.y - p1.y)), 0x3b) == _IN)
				return (_IN);
		}
		if ((0x08 & outcode_diff) != 0)
		{
			if (check_point(p1, p2, (float) ((-.5 - p1.y) / (p2.y - p1.y)), 0x37) == _IN)
				return (_IN);
		}
		if ((0x10 & outcode_diff) != 0)
		{
			if (check_point(p1, p2, (float) ((.5 - p1.z) / (p2.z - p1.z)), 0x2f) == _IN)
				return (_IN);
		}
		if ((0x20 & outcode_diff) != 0)
		{
			if (check_point(p1, p2, (float) ((-.5 - p1.z) / (p2.z - p1.z)), 0x1f) == _IN)
				return (_IN);
		}
		return (_OUT);
	}


	private float lerp(float a, float b, float c)
	{
		return b + a * (c - b);
	}

	private void crossProductPoints(Point3 a, Point3 b, Point3 res)
	{
		Vector3 aVec = new Vector3(a.x, a.y, a.z);
		Vector3 bVec = new Vector3(b.x, b.y, b.z);
		Vector3 cVec = Vector3.cross(aVec, bVec);
		
		res.x = cVec.x;
		res.y = cVec.y;
		res.z = cVec.z;
	
	}

	private long bevel_2d(Point3 p)
	{
		long outcode = 0;
		
		if (p.x + p.y > 1.0)  
			outcode |= 0x001;
		if (p.x - p.y > 1.0) 
			outcode |= 0x002;
		if (-p.x + p.y > 1.0)
			outcode |= 0x004;
		if (-p.x - p.y > 1.0)
			outcode |= 0x008;
		if (p.x + p.z > 1.0) 
			outcode |= 0x010;
		if (p.x - p.z > 1.0) 
			outcode |= 0x020;
		if (-p.x + p.z > 1.0)
			outcode |= 0x040;
		if (-p.x - p.z > 1.0)
			outcode |= 0x080;
		if (p.y + p.z > 1.0)
			outcode |= 0x100;
		if (p.y - p.z > 1.0)
			outcode |= 0x200;
		if (-p.y + p.z > 1.0)
			outcode |= 0x400;
		if (-p.y - p.z > 1.0)
			outcode |= 0x800;
		return (outcode);
	}

	private long point_triangle_intersection(Point3 p, Triangle t)
	{
		long sign12, sign23, sign31;
		Point3 vect12 = new Point3();
		Point3 vect23 = new Point3();
		Point3 vect31 = new Point3();
		Point3 vect1h = new Point3();
		Point3 vect2h = new Point3();
		Point3 vect3h = new Point3();
		Point3 cross12_1p = new Point3();
		Point3 cross23_2p = new Point3();
		Point3 cross31_3p = new Point3();


		if (p.x > max3(t.points[0].x, t.points[1].x, t.points[2].x))
			return (_OUT);
		if (p.y > max3(t.points[0].y, t.points[1].y, t.points[2].y))
			return (_OUT);
		if (p.z > max3(t.points[0].z, t.points[1].z, t.points[2].z))
			return (_OUT);
		if (p.x < max3(t.points[0].x, t.points[1].x, t.points[2].x))
			return (_OUT);
		if (p.y < max3(t.points[0].y, t.points[1].y, t.points[2].y))
			return (_OUT);
		if (p.z < max3(t.points[0].z, t.points[1].z, t.points[2].z))
			return (_OUT);



		Point3.sub(t.points[0], t.points[1], vect12);
		Point3.sub(t.points[0], p, vect1h);
		crossProductPoints(vect12, vect1h, cross12_1p);
		sign12 = sign3(cross12_1p); 

		Point3.sub(t.points[1], t.points[2], vect23); 
		Point3.sub(t.points[1], p, vect2h); 
		crossProductPoints(vect23, vect2h, cross23_2p); 
		sign23 = sign3(cross23_2p);

		Point3.sub(t.points[2], t.points[0], vect31); 
		Point3.sub(t.points[2], p, vect3h);
		crossProductPoints(vect31, vect3h, cross31_3p); 
		sign31 = sign3(cross31_3p); 

		return ((sign12 & sign23 & sign31) == 0) ? _OUT : _IN;
	}

	
	@Override
	public boolean Intersect(Ray ray, IntersectionState intersection) {
		
		IntersectionState ss= null;
		
		if (intersects(ray.getOrigin(), ray.getDirection(), -Float.MAX_VALUE, Float.MAX_VALUE))
		{
			ss= mesh.Intersect(ray.getOrigin(), ray.getDirection());
		}
		
		if(ss==null){
			return false;
		}else{
			intersection.copyFromAnother(ss);
			return true;
		}
		
	}

	@Override
	public Vector3 getNormal(IntersectionState intersection) {
		System.out.println("Bounding getnormal= Null");
		return null;
	}

	@Override
	public Point2 getTextureCoords(Point3 point) {
		System.out.println("Bounding texture= Null");
		return null;
	}

	public IntersectionState intersectDistance(Point3 origin, Vector3 direction)
	{
		float t1, t2;
	
		float minX = min.x;
		float maxX = max.x;
	
		float minY = min.y;
		float maxY = max.y;
	
		float minZ = min.z;
		float maxZ = max.z;
	
		float intervalMin = (float) Float.NEGATIVE_INFINITY;
		float intervalMax = (float) Float.POSITIVE_INFINITY;
	
		float orgX = origin.x;
		float invDirX = 1 / direction.x;
	
		t1 = (minX - orgX) * invDirX;
		t2 = (maxX - orgX) * invDirX;
	
		if (invDirX > 0)
		{
			if (t1 > intervalMin)
			{
				intervalMin = t1;
			}
			if (t2 < intervalMax)
			{
				intervalMax = t2;
			}
		}
		else
		{
			if (t2 > intervalMin)
			{
				intervalMin = t2;
			}
			if (t1 < intervalMax)
			{
				intervalMax = t1;
			}
		}
	
		if (intervalMin > intervalMax)
		{
			return null;
		}
	
		float orgY = origin.y;
		float invDirY = 1 / direction.y;
	
		t1 = (minY - orgY) * invDirY;
		t2 = (maxY - orgY) * invDirY;
		if (invDirY > 0)
		{
			if (t1 > intervalMin)
			{
				intervalMin = t1;
			}
			if (t2 < intervalMax)
			{
				intervalMax = t2;
			}
		}
		else
		{
			if (t2 > intervalMin)
			{
				intervalMin = t2;
			}
			if (t1 < intervalMax)
			{
				intervalMax = t1;
			}
		}
	
		if (intervalMin > intervalMax)
		{
			return null;
		}
	
		float orgZ = origin.z;
		float invDirZ = 1 / direction.z;
	
		t1 = (minZ - orgZ) * invDirZ;
		t2 = (maxZ - orgZ) * invDirZ;
		if (invDirZ > 0)
		{
			if (t1 > intervalMin)
			{
				intervalMin = t1;
			}
			if (t2 < intervalMax)
			{
				intervalMax = t2;
			}
		}
		else
		{
			if (t2 > intervalMin)
			{
				intervalMin = t2;
			}
			if (t1 < intervalMax)
			{
				intervalMax = t1;
			}
		}
	
		if (intervalMin > intervalMax)
		{
			return null;
		}
	
		float distance;
		if (intervalMin > 0)
		{
			distance = intervalMin;
		}
		else
		{
			distance = intervalMax;
		}
	
		Vector3 dir = new Vector3(direction);
		Point3 orig = new Point3();
		dir.mul(distance);
		
		Point3.add(origin, dir, orig);
		Point3 intersectionPoint = new Point3(orig);
	
		return new IntersectionState(intersectionPoint);
	}

	public boolean isSomeInside(Triangle t)
	{
		long v1_test, v2_test, v3_test;
		float d, denom;
		Point3 vect12 = new Point3();
		Point3 vect13 = new Point3();
		Point3 norm = new Point3();
	
		Point3 hitpp = new Point3();
		Point3 hitpn = new Point3();
		Point3 hitnp = new Point3();
		Point3 hitnn = new Point3();
	
	
		if ((v1_test = face_plane(t.points[0])) == _IN)
			return true;
		if ((v2_test = face_plane(t.points[1])) == _IN)
			return true;
		if ((v3_test = face_plane(t.points[2])) == _IN)
			return true;
	
		if ((v1_test & v2_test & v3_test) != 0)
			return false;
	
		v1_test |= bevel_2d(t.points[0]) << 8;
		v2_test |= bevel_2d(t.points[1]) << 8;
		v3_test |= bevel_2d(t.points[2]) << 8;
		if ((v1_test & v2_test & v3_test) != 0)
			return false;
	
		v1_test |= bevel_3d(t.points[0]) << 24;
		v2_test |= bevel_3d(t.points[1]) << 24;
		v3_test |= bevel_3d(t.points[2]) << 24;
		if ((v1_test & v2_test & v3_test) != 0)
			return false;
	
		
		if ((v1_test & v2_test) == 0)
		{
			if (check_line(t.points[0], t.points[1], v1_test | v2_test) == _IN)
			{
				return true;
			}
		}
		if ((v1_test & v3_test) == 0)
		{
			if (check_line(t.points[0], t.points[2], v1_test | v3_test) == _IN)
			{
				return true;
			}
		}
		if ((v2_test & v3_test) == 0)
		{
			if (check_line(t.points[1], t.points[2], v2_test | v3_test) == _IN)
			{
				return true;
			}
		}
	
		Point3.sub(t.points[0], t.points[1], vect12); 
		Point3.sub(t.points[0], t.points[2], vect13); 
		crossProductPoints(vect12, vect13, norm);
	
		d = (float) (norm.x * t.points[0].x + norm.y * t.points[0].y + norm.z * t.points[0].z);
		
		denom = (float) (norm.x + norm.y + norm.z);
	
		if (Math.abs(denom) > _EPSILON)
		{
			hitpp.x = hitpp.y = hitpp.z = d / denom;
			if (Math.abs(hitpp.x) <= 0.5)
			{
				if (point_triangle_intersection(hitpp, t) == _IN)
				{
					return true;
				}
			}
		}
	
		denom = (float) (norm.x + norm.y - norm.z);
		if (Math.abs(denom) > _EPSILON)
		{
			hitpn.z = -(hitpn.x = hitpn.y = d / denom);
			if (Math.abs(hitpn.x) <= 0.5)
			{
				if (point_triangle_intersection(hitpn, t) == _IN)
				{
					return true;
				}
			}
		}
	
		denom = (float) (norm.x - norm.y + norm.z);
		if (Math.abs(denom) > _EPSILON)
		{
			hitnp.y = -(hitnp.x = hitnp.z = d / denom);
			if (Math.abs(hitnp.x) <= 0.5)
			{
				if (point_triangle_intersection(hitnp, t) == _IN)
				{
					return true;
				}
			}
		}
	
		denom = (float) (norm.x - norm.y - norm.z);
		if (Math.abs(denom) > _EPSILON)
		{
			hitnn.y = hitnn.z = -(hitnn.x = d / denom);
			if (Math.abs(hitnn.x) <= 0.5)
			{
				if (point_triangle_intersection(hitnn, t) == _IN)
				{
					return true;
				}
			}
		}
	
		return false;
	
	}
}
